/*
 * @Author: aleimu
 * @Date: 2021-03-26 10:22:50
 * @Description: <<useEffect 完整指南>> 中的示例
 */

import React, { useState, useEffect, useRef, useReducer } from "react";
import ReactDOM from "react-dom";

function Out() {
    console.log("--------Out rerender----------")
    return (<div>另一个组件</div>)
}
function Example() {
    const [count, setCount] = useState(0);

    // 每一个effect版本“看到”的count值都来自于它属于的那次渲染 
    useEffect(() => {
        document.title = `You clicked ${count} times`;
    });
    useEffect(() => {
        setTimeout(() => {
            console.log(`You clicked ${count} times`);
        }, 3000);
    });

    function handleAlertClick() {
        setTimeout(() => {
            alert('You clicked on: ' + count); // 闭包?
        }, 3000);
    }
    // 传入参数,也是和上面的下偶偶相同,count 只属于调用的那次的渲染.
    function handleAlertClick2(count) {
        console.log(count)
        setTimeout(() => {
            alert('You clicked on: ' + count);
        }, 3000);
    }
    console.log('--------Example rerender---------')
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
            <button onClick={handleAlertClick}>
                Show alert1
            </button>
            <button onClick={() => handleAlertClick2(count)}>
                Show alert2
            </button>
        </div>
    );
}

// 使用useRef 读取最新的值而不是捕获的值
function MessageThread() {
    const [message, setMessage] = useState('');

    // Keep track of the latest value.
    const latestMessage = useRef('');
    useEffect(() => {
        latestMessage.current = message; // 重新指定
    });

    const showMessage = () => {
        alert('You said: ' + latestMessage.current);
    };

    const handleSendClick = () => {
        setTimeout(showMessage, 3000);
    };

    const handleMessageChange = (e) => {
        setMessage(e.target.value);
    };

    return (
        <>
            <input value={message} onChange={handleMessageChange} />
            <button onClick={handleSendClick}>Send</button>
        </>
    );
}

// useEffect 依赖的几种处理方式
/*
1. 第一种策略是在依赖中包含所有effect中用到的组件内的值,如 [count]
2. 第二种策略是修改effect内部的代码以确保它包含的值只会在需要的时候发生变更。我们不想告知错误的依赖 - 我们只是修改effect使得依赖更少。让 count 这个依赖参数自给自足.既不在依赖外部每次渲染时最新的 count 值.
*/
function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const id = setInterval(() => {
            // setCount(count + 1);
            setCount(count => count + 1);
        }, 1000);
        return () => clearInterval(id);
    }, []); // 这里不明确依赖的参数count,就会导致计数到 1 就会停止,因为每次渲染,下面的这种提示就是说的这个事,需要注意.
    // React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array. You can also do a functional update 'setCount(c => ...)' if you only need 'count' in the 'setCount' call  react-hooks/exhaustive-deps
    //在第一次渲染中，count是0。因此，setCount(count + 1)在第一次渲染中等价于setCount(0 + 1)。既然我们设置了[]依赖，effect不会再重新运行，它后面每一秒都会调用setCount(0 + 1)

    return <h1>{count}</h1>;
}


// 组件依赖另一个组件
function CounterNoStep() {

    const [count, setCount] = useState(0);
    const [step, setStep] = useState(1);

    useEffect(() => {
        const id = setInterval(() => {
            setCount(c => c + step);
        }, 1000);
        return () => clearInterval(id);
    }, [step]);

    return (
        <>
            <h1>{count}</h1>
            <input value={step} onChange={e => setStep(Number(e.target.value))} />
        </>
    );
}

// 当你想更新一个状态，并且这个状态更新依赖于另一个状态的值时，你可能需要用useReducer去替换它们。
// useReducer是Hooks的“作弊模式”。它可以把更新逻辑和描述发生了什么分开。结果是，这可以帮助我移除不必需的依赖，避免不必要的effect调用。
function CounterReducer() {
    const [state, dispatch] = useReducer(reducer, initialState);
    const { count, step } = state;

    useEffect(() => {
        const id = setInterval(() => {
            dispatch({ type: 'tick' });
        }, 1000);
        return () => clearInterval(id);
    }, [dispatch]);
    /*
    在之前渲染中调用的reducer怎么“知道”新的props？答案是当你dispatch的时候，
    React只是记住了action - 它会在下一次渲染中再次调用reducer。
    在那个时候，新的props就可以被访问到，而且reducer调用也不是在effect里。

    感觉是上一次渲染将 state 存入了 dispatch 中,之后的渲染是调用 dispatch 运行,更新 state.
    */
    return (
        <>
            <h1>{count}</h1>
            <input value={step} onChange={e => {
                dispatch({
                    type: 'step',
                    step: Number(e.target.value)
                });
            }} />
        </>
    );
}

const initialState = {
    count: 0,
    step: 1,
};

function reducer(state, action) {
    const { count, step } = state;// 获取本次渲染需要的参数
    if (action.type === 'tick') {
        return { count: count + step, step };//返回新的 state
    } else if (action.type === 'step') {
        return { count, step: action.step };
    } else {
        throw new Error();
    }
}

/*
1. 渲染是按组件来进行的,Out 组件没有变动则不会重渲染,也就不会执行 console.log
2. 点击Show alert 后再点击Click me,等待弹窗显示,注意count的值
3. 事件处理函数“属于”某一次特定的渲染，当你点击的时候，它会使用那次渲染中counter的状态值。在任意一次渲染中，props和state是始终保持不变的。如果props和state在不同的渲染中是相互独立的，那么使用到它们的任何值也是独立的（包括事件处理函数）。它们都“属于”一次特定的渲染。即便是事件处理中的异步函数调用“看到”的也是这次渲染中的count值。
4. 每一个组件内的函数（包括事件处理函数，effects，定时器或者API调用等等）会捕获某次渲染中定义的props和state。
5. 想在effect的回调函数里读取最新的值而不是捕获的值。最简单的实现方法是使用refs
*/

const App = () => {
    return (
        <div>
            <Example />
            <Out />
            <MessageThread />
            <Counter />
            <CounterNoStep />
            <CounterReducer />
        </div>
    )
}

export default App





/*
确定 UI state 的最小（且完整）表示:
1. 该数据是否是由父组件通过 props 传递而来的？如果是，那它应该不是 state。
2. 该数据是否随时间的推移而保持不变？如果是，那它应该也不是 state。
3. 你能否根据其他 state 或 props 计算出该数据的值？如果是，那它也不是 state。

*/